/*
 * Copyright (c) 2020 KingSoft.com, Inc. All Rights Reserved
 *
 * @file curl_manager.cc
 * @author lihaibing(lihaibing@kingsoft.com)
 * @date 2020/05/17 20:33:07
 * @brief
 *
 *
*/

#include "curl_manager.h"
#include <ctime>
#include <algorithm>
#include <iostream>
#include "util.h"
#include <cstring>
#include "SignerV2.h"


namespace ks3 {
namespace sdk {

static const std::string kHTTP = "HTTP";


CURLManager::CURLManager(const std::string& host, bool use_https, long socket_timeout, long connect_timeout) {
    use_https_ = use_https;
    host_ = host;
    socket_timeout_ = socket_timeout;
    connect_timeout_ = connect_timeout;
}

int CURLManager::Init() {
    handler_ = curl_easy_init();
    if (NULL == handler_) {
        return -1;
    }

    SetDefaultOptions(handler_, connect_timeout_, socket_timeout_);

    return 0;
}

void CURLManager::SetMethod(MethodType method_type) {
	switch(method_type) {
	case PUT_METHOD:
	    // 如果使用CURLOPT_PUT选项设置put请求，会自动设置CURLOPT_UPLOAD开启上传模式。
	    // 这将导致即使put请求没有请求体，curl_easy_perform也会一直阻塞等待请求体
	    // curl_easy_setopt(handler_, CURLOPT_UPLOAD, 1L);
	    // curl_easy_setopt(handler_, CURLOPT_PUT, 1L);
		curl_easy_setopt(handler_, CURLOPT_CUSTOMREQUEST, "PUT");
		break;
    case POST_METHOD:
        curl_easy_setopt(handler_, CURLOPT_POST, 1L);
		break;
	case DELETE_METHOD:
		curl_easy_setopt(handler_, CURLOPT_CUSTOMREQUEST, "DELETE");
        break;
	case HEAD_METHOD:
		curl_easy_setopt(handler_, CURLOPT_NOBODY, 1L);
        break;
    default:
        break;
	}
}

static size_t upload_data(void *ptr, size_t size, size_t nmemb, void *buf) {
	KS3Context* ctx = (KS3Context*) buf;
	if (ctx->uploaded_len >= ctx->data_len) {
		return 0;
	}

	unsigned int to_read_len = nmemb * size;
    if (to_read_len == 0) {
        return 0;
    }
    
    unsigned int remain_len = ctx->data_len - ctx->uploaded_len;
    if (to_read_len > remain_len) {
        to_read_len = remain_len;
    }

	memcpy(ptr, ctx->data + ctx->uploaded_len, to_read_len);
    ctx->uploaded_len += to_read_len;

	return to_read_len;
}

static size_t read_ks3_headers(char* buffer, size_t size, size_t nmemb, void *stream) {
    const size_t num_bytes = size * nmemb;
    if (num_bytes <= 0) {
        return 0;
    }

    auto* res = static_cast<KS3Response*>(stream);
    res->headers_buffer.append(buffer, num_bytes);
    // The header callback is called once for each header and only complete header lines are passed on to the callback.
    // 即每次buffer都是一个header
    std::string line(buffer);

    const auto key_len = line.find(':');
    if (key_len != std::string::npos)
    {
        auto value_len = line.rfind('\r');
        if (value_len != std::string::npos)
        {
            value_len = value_len - key_len - 2;
        }
        // header格式为：key: value\r
        std::string key = line.substr(0, key_len);
        std::string value = line.substr(key_len + 2, value_len);
        res->res_headers.insert(std::pair<std::string, std::string>(key, value));
    }
    else if (line.find(kHTTP) == 0)
    {
        // 获取status_msg HTTP/1.1 200 OK\r\n
        const auto first_space_pos = line.find(' ');
        if (first_space_pos == std::string::npos) return num_bytes;
        const auto second_space_pos = line.find(' ', first_space_pos + 1);
        if (second_space_pos == std::string::npos) return num_bytes;
        const auto end_pos = line.rfind('\r');
        if (end_pos == std::string::npos) return num_bytes;
        res->status_msg.append(line.substr(second_space_pos + 1, end_pos - second_space_pos - 1));
    }

    return num_bytes;
}

static size_t read_ks3_response(char* buffer, size_t size, size_t nmemb, void *stream)
{
    const size_t num_bytes = size * nmemb;
    if (num_bytes <= 0) {
        return 0;
    }

    auto* res = static_cast<KS3Response*>(stream);
    // 如果是第一次读取响应体，则获取响应码
    if (res->curl_handler != nullptr && res->first_read_body)
    {
        curl_easy_getinfo(res->curl_handler, CURLINFO_RESPONSE_CODE, &(res->status_code));
        res->first_read_body = false;
    }
    // 如果没有设置response_output或者响应码不是2xx，则将响应内容追加到content中（用于接收错误信息或其他）
    if (res->response_output == nullptr || res->status_code / 100 != 2)
    {
        res->content.append(buffer, num_bytes);
    }
    else
    {
        const std::shared_ptr<std::iostream>& content = res->response_output;
        content->write(buffer, static_cast<std::streamsize>(num_bytes));
    }

    return num_bytes;
}

static std::string kHeaderDelimiter = "\r\n";

static void Trim(std::string& value) {
    // trim space
    size_t tmp_space_pos = value.find_first_not_of(" ");
    if (tmp_space_pos != std::string::npos) {
        value.erase(0, tmp_space_pos);
    }
    tmp_space_pos = value.find_last_not_of(" ");
    if (tmp_space_pos != std::string::npos) {
        value.erase(tmp_space_pos + 1);
    }
}

/**
 * @deprecated 此方法的逻辑已在read_ks3_headers和read_ks3_response中实现
 */
void CURLManager::ParseResponseHeaders(KS3Response* response) {
    if (handler_ == NULL || response == NULL) {
        return;
    }

    // 1. get http status code
    curl_easy_getinfo(handler_, CURLINFO_RESPONSE_CODE, &(response->status_code));

    // 2. get http status msg
	// 2.1 get first line: start with 'HTTP'
    size_t start_pos = 0;
    size_t end_pos = response->headers_buffer.find(kHeaderDelimiter);
    while (end_pos != std::string::npos) {
        // substr应该填起始位置和子串长度，而不是起始位置和结束位置
        std::string tmp_str = response->headers_buffer.substr(start_pos, end_pos);
        if (tmp_str.find(kHTTP) == 0) {
            // split "HTTP/xxxx status_code status_msg" by space
            size_t space_pos = tmp_str.find_last_of(" ");
            if (space_pos != std::string::npos) {
                // copy status msg
                response->status_msg.append(tmp_str.substr(space_pos + 1));
            }
        } else {
            // parse all headers
            size_t tmp_space = tmp_str.find(":");
            if (tmp_space != std::string::npos) {
                std::string header_name;
                header_name.assign(tmp_str.substr(0, tmp_space));
                Trim(header_name);

                std::string header_value;
                header_value.assign(tmp_str.substr(tmp_space + 1));
                Trim(header_value);

                response->res_headers.insert(std::pair<std::string, std::string>(header_name, header_value));
            }
        }

        start_pos = end_pos + kHeaderDelimiter.size();
        end_pos = response->headers_buffer.find(kHeaderDelimiter, start_pos);
    }
}

int CURLManager::Call(MethodType method, const KS3Context& ctx, KS3Response* response) {
    ScopedLocker<MutexLock> lock(lock_);

    curl_easy_reset(handler_);
    SetDefaultOptions(handler_, connect_timeout_, socket_timeout_);
    // 1. construct url
    std::string url;
    url.reserve(512);
    if (use_https_) {
        url.append("https://");
    } else {
        url.append("http://");
    }
    bool is_ignore_bucket = true;
    if (!ctx.bucket.empty() && host_.compare(0, ctx.bucket.length(), ctx.bucket) != 0) {
        url.append(ctx.bucket);
        url.append("."); 
    }
    url.append(host_);
    GetQueryString(ctx, false, &url, is_ignore_bucket);

    const std::string signature = SignerV2::Sign(ctx, method);

    // auth header;
    std::string auth_header;
    auth_header.append("Authorization:KSS ");
    auth_header.append(ctx.accesskey);
    auth_header.append(":");
    auth_header.append(signature);
    // 2. set url
	curl_slist* http_header = NULL;

    // 3. set headers
    std::map<std::string, std::string>::const_iterator it = ctx.headers.cbegin();
    for (; it != ctx.headers.cend(); ++it) {
        std::string header = it->first;
        header.append(":");
        header.append(it->second);
        http_header = curl_slist_append(http_header, header.c_str());
    }
    // append auth header
    http_header = curl_slist_append(http_header, auth_header.c_str());

    // 4. set op
    if (use_https_) {
        curl_easy_setopt(handler_, CURLOPT_SSL_VERIFYPEER, 1L);
        curl_easy_setopt(handler_, CURLOPT_SSL_VERIFYHOST, 2L);
    }

    http_header = curl_slist_append(http_header, "Expect:");    //禁止curl的试探

    curl_easy_setopt(handler_, CURLOPT_NOSIGNAL, 1L);
    curl_easy_setopt(handler_, CURLOPT_URL, url.c_str());
    SetMethod(method);
    curl_easy_setopt(handler_, CURLOPT_HTTPHEADER, http_header);

    if (method == POST_METHOD) {
        curl_easy_setopt(handler_, CURLOPT_POSTFIELDS, ctx.data);
        curl_easy_setopt(handler_, CURLOPT_POSTFIELDSIZE, ctx.data_len);
    }

    if (ctx.data != NULL) {
        if (method == PUT_METHOD)
        {
            // 有请求体时才设置CURLOPT_UPLOAD
            curl_easy_setopt(handler_, CURLOPT_UPLOAD, 1L);
        }
        // put data to ks3
        // set read content func
        curl_easy_setopt(handler_, CURLOPT_READFUNCTION, upload_data);
        curl_easy_setopt(handler_, CURLOPT_READDATA, &ctx);

        curl_easy_setopt(handler_, CURLOPT_INFILE, &ctx);
        curl_easy_setopt(handler_, CURLOPT_INFILESIZE_LARGE, ctx.data_len);
    }

    // set read header callback func
    curl_easy_setopt(handler_, CURLOPT_HEADERFUNCTION, read_ks3_headers);
    curl_easy_setopt(handler_, CURLOPT_HEADERDATA, response);
    // set read body callback func
    response->curl_handler = handler_;
    curl_easy_setopt(handler_, CURLOPT_WRITEFUNCTION, read_ks3_response);
    curl_easy_setopt(handler_, CURLOPT_WRITEDATA, response);

	CURLcode res = curl_easy_perform(handler_);
    curl_easy_getinfo(response->curl_handler, CURLINFO_RESPONSE_CODE, &(response->status_code));
    // if (res == CURLE_OK) {
    //     ParseResponseHeaders(response);
    // }

	curl_slist_free_all(http_header);
    return res;
}

int CURLManager::Put(const KS3Context& ctx, KS3Response* response) {
    return Call(PUT_METHOD, ctx, response);
}

int CURLManager::Post(const KS3Context& ctx, KS3Response* response) {
    return Call(POST_METHOD, ctx, response);
}

int CURLManager::Get(const KS3Context& ctx, KS3Response* response) {
    return Call(GET_METHOD, ctx, response);
}

int CURLManager::Delete(const KS3Context& ctx, KS3Response* response) {
    return Call(DELETE_METHOD, ctx, response);
}

int CURLManager::Head(const KS3Context& ctx, KS3Response* response) {
    return Call(HEAD_METHOD, ctx, response);
}

}
}
/* vim: set expandtab ts=4 sw=4 sts=4 tw=100: */
